package com.tjhk.netty.serialize;

import com.tjhk.netty.serialize.annotation.Field;
import com.tjhk.netty.util.Bcd;
import com.tjhk.netty.util.ByteBufUtils;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.nio.charset.Charset;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
 * BasicField序列化工具
 * 顺序序列化@Field注解字段
 * 无@Field字段可根据自己的情况处理
 */
public class BasicFieldSerializeUtil {
    private static final Logger log = LoggerFactory.getLogger(BasicFieldSerializeUtil.class.getSimpleName());

    protected static Map<String,List<BasicField>> fieldCollects = new ConcurrentHashMap<>(60);

    public static void loadFields(Class<?> clazz){
        fieldCollects.put(clazz.getName(),findFields(clazz));
    }

    protected static List<BasicField> findFields(Class<?> clazz){
        List<BasicField> basicFieldList = new ArrayList<>();
        java.lang.reflect.Field[] fields = clazz.getDeclaredFields();
        for (java.lang.reflect.Field field : fields) {
            if (field.isAnnotationPresent(Field.class)){
                try {
                    PropertyDescriptor descriptor = new PropertyDescriptor(field.getName(), clazz);
                    field.getType();
                    Field f = field.getAnnotation(Field.class);
                    basicFieldList.add(new BasicField(f,descriptor, field.getType()));
                } catch (IntrospectionException e) {
                    e.printStackTrace();
                }
            }
        }
        List<BasicField> sorted = basicFieldList.stream().sorted(Comparator.comparingInt(BasicField::getIndex)).collect(Collectors.toList());
        return sorted;
    }

    public static <T> ByteBuf writeTo(T t){
        List<BasicField> fields = fieldCollects.get(t.getClass().getName());
        if (fields == null){
            loadFields(t.getClass());
        }
        fields = fieldCollects.get(t.getClass().getName());
        ByteBuf out = Unpooled.buffer(32);
        for (BasicField field:fields) {
            try {
                Object value = field.getReadMethod().invoke(t);
                switch (field.getField().type()) {
                    case BYTE:
                        out.writeByte((int) value);
                        break;
                    case WORD:
                        out.writeShort((int) value);
                        break;
                    case DWORD:
                        out.writeInt((int) value);
                        break;
                    case QWORD:
                        out.writeLong((long) value);
                        break;
                    case BCD8421:
                        if (LocalDateTime.class.isAssignableFrom(value.getClass())){
                            out.writeBytes(Bcd.from((LocalDateTime) value));
                        }else {
                            out.writeBytes(Bcd.from((String) value));
                        }
                        break;
                    case BYTES:
                        out.writeBytes((byte[]) value);
                        break;
                    case STRING:
                        byte[] bytes = ((String)value).getBytes(Charset.forName(field.getField().charset()));
                        if (field.getLength() > bytes.length){
                            byte[] pads = new byte[field.getLength() - bytes.length];
                            out.writeBytes(pads);
                            out.writeBytes(bytes);
                        }else {
                            out.writeBytes(bytes);
                        }
                        break;
                    default:
                        throw new RuntimeException("不支持的类型转换");
                }
            } catch (Exception e) {
                log.error("<<<<<读取参数错误，参数：{}，请检查是否设置GET方法！或数据参数类型错误！或参数长度是否正确！，err:{}",field.getDesc(),e);
                new RuntimeException();
            }
        }
        return out;
    };
    public static <T> T readFrom(ByteBuf input, T t){
        List<BasicField> fields = fieldCollects.get(t.getClass().getName());
        if (fields == null){
            loadFields(t.getClass());
        }
        fields = fieldCollects.get(t.getClass().getName());
        for (BasicField field:fields) {
            try {
                switch (field.getField().type()) {
                    case BYTE:
                        field.getWriteMethod().invoke(t,(int)input.readUnsignedByte());
                        break;
                    case WORD:
                        field.getWriteMethod().invoke(t,input.readUnsignedShort());
                        break;
                    case DWORD:
                        field.getWriteMethod().invoke(t,input.readInt());
                        break;
                    case QWORD:
                        field.getWriteMethod().invoke(t,input.readLong());
                        break;
                    case BCD8421:
                        if (LocalDateTime.class.isAssignableFrom(field.getFieldType())){
                            field.getWriteMethod().invoke(t,Bcd.toDateTime(ByteBufUtils.readFixLength(input,6)));
                        }else {
                            field.getWriteMethod().invoke(t,Bcd.toString(ByteBufUtils.readFixLength(input,field.getLength())));
                        }
                        break;
                    case BYTES:
                        field.getWriteMethod().invoke(t,ByteBufUtils.readFixLength(input,field.getLength()));
                        break;
                    case STRING:
                        int len = field.getLength() > 0 && field.getLength() < input.readableBytes() ? field.getLength() : input.readableBytes();
                        field.getWriteMethod().invoke(t,new String(ByteBufUtils.readFixLength(input,len),Charset.forName(field.getField().charset())));
                        break;
                    default:
                        throw new RuntimeException("不支持的类型转换");
                }
            } catch (Exception e) {
                log.error(">>>>>写入参数错误，参数：{}，请检查是否设置SET方法！或数据参数类型错误！或参数长度是否正确！，err:{}",field.getDesc(),e);
                new RuntimeException();
            }
        }
        return t;
    }
}
